﻿using System;
using System.Windows.Controls;
using System.ComponentModel;
using System.Windows.Input;

namespace WPFCustomTextboxControls.Time
{
    public class MaskedTimeTextBox : MaskedTextBox
    {
        #region Member
        private int _stunde;
        private int _minute;
        #endregion

        #region Properties
        /// <summary>
        /// Es kann sich die Maske nur angeschaut werden, nicht geändert.
        /// </summary>
        private new string Mask
        {
            get
            {
                if (_mask != null)
                {
                    return _mask.Mask;
                }

                return string.Empty;
            }
        }

        /// <summary>
        /// Die Aktuelle stunde die Im Kontrol angezeigt wird
        /// </summary>
        public int Stunde
        {
            get { return _stunde; }
            set { _stunde = value; }
        }

        /// <summary>
        /// Die Aktuelle Minute die im control angezeigt wird.
        /// </summary>
        public int Minute
        {
            get { return _minute; }
            set { _minute = value; }
        }
        #endregion

        #region Konstruktor
        /// <summary>
        /// Initialisieren der MaskedTextBox für die Zeit.
        /// </summary>
        public MaskedTimeTextBox()
        {
            //Initialisieren und anzeigen der Zeitmaske
           _mask = new MaskedTextProvider("00:00");
           this.Text = _mask.ToDisplayString();

            //Zwischenspeichern welche Werte die einzelnen Minuten und Stunden haben.
            Stunde = 0;
            Minute = 0;
            SetText();
        }
        #endregion

        #region Functions/Subs
        /// <summary>
        /// Setzten des aktuellen Textes, anhand der 4 "ZeitVariablen"
        /// </summary>
        private void SetText()
        {
            //damit dieser nach dem Setzten des Textes wieder an die richtige Position gesetzt werden kann, vorher den aktuellen Cursor Index merken im Textfeld
            int tCarret = this.CaretIndex;
            this.Text = string.Format("{0:00}:{1:00}", Stunde, Minute);
            this.CaretIndex = tCarret;
        }

        /// <summary>
        /// Überprüft den übergebenen String und setzt alle Zeitwerte.
        /// </summary>
        /// <param name="zeit">Zeot als String  z.b. "08:45" oder "12:01"</param>
        private void CheckTime(string zeit)
        {
           //Setzten der Stunden und Minuten die überprüft werden sollen auf "richtigkeit"
            if (zeit != null && zeit.Contains(":"))
            {
                try
                {
                    //Auslesen der Stunde aus dem String.
                    Stunde = int.Parse(zeit.Split(':')[0]);
                }
                catch (Exception)
                {
                    Stunde = 0;
                }

                try
                {
                    //Auslesen der Minute aus dem String.
                    Minute = int.Parse(zeit.Split(':')[1]);
                }
                catch (Exception)
                {
                    Minute = 0;
                }
            }
            else
            {
                throw new Exception("Bitte geben Sie einen gültigen Wert an z.B. 08:13");
            }

            //Stunden sowie Minuten prüfen ob die Werte gültig sind und wenn nicht die Werte "zurücksetzten" auf 00:00
           if(Stunde > 24 || Stunde < 0)
           {
               Stunde = 00;
           }
           
           if (Minute > 59 || Minute < 0)
           {
               Minute = 00;
           }

            SetText();
        }

        /// <summary>
        /// Fügt die übergebenen Stunden hinzu und gibt die aktuelle Stunde zurück
        /// </summary>
        /// <param name="stunden">Die Stunden die hinzuaddiert werden sollen</param>
        /// <returns>aktuelle Stunde</returns>
        public int AddStunden(int stunden)
        {
            int tNeueStunde = Stunde + stunden;
            //Wenn die Addition der beiden Stundebeträge größer 23 ist mus zusätzlich berechnet werden wie spät es ist.
            if (tNeueStunde > 23)
            {
                Stunde = (tNeueStunde % 24);
            }
            else
            {
                Stunde = tNeueStunde;
            }

            return Stunde;
        }

        /// <summary>
        /// Subtrahiert die übergebenen Stunden von der aktuellen Stundenanzahl
        /// </summary>
        /// <param name="stunden">Die Stunden die subtrahiert werden sollen</param>
        /// <returns>aktuelle Stunde</returns>
        public int SubStunden(int stunden)
        {
            int tNeueStunde = Stunde - stunden;
            if (tNeueStunde < 0)
            {
                //Normalisieren des übergebenen Wertes
                int tNorm = stunden % 24;
                int tDiff = -1 * (Stunde - tNorm);
                if (tDiff < 0)
                {
                    tDiff = tDiff * -1;
                }
                Stunde = 24 - tDiff;
            }
            else
            {
                Stunde = tNeueStunde;
            }

            return Stunde;
        }

        /// <summary>
        /// Subdrahiert die übergebenen Minuten von der Zeit
        /// </summary>
        /// <param name="minuten">Die Minuten die subdrahiert werden sollen</param>
        /// <returns>aktuelle Minute</returns>
        public int AddMinuten(int minuten)
        {
            int tNeueMinute = Minute + minuten;

            if (tNeueMinute > 59)
            {
                //Berechnen der Stunden die hinzugefügt werden sollen
                int tStunden = tNeueMinute/60;
                //Die Stunden zur Zeit dazuzählen
                AddStunden(tStunden);
                //Die aktuelle Minute mittels Mod ausrechnen
                Minute = tNeueMinute%60;
            }
            else
            {
                Minute = tNeueMinute;
            }

            return Minute;
        }

        /// <summary>
        /// Addiert die übergebenen Minuten zur Zeit
        /// </summary>
        /// <param name="minuten">Die Minuten die addiert werden solle</param>
        /// <returns>aktuelle Minute</returns>
        public int SubMinuten(int minuten)
        {
            int tNeueMinute = Minute - minuten;
            if (tNeueMinute < 0)
            {
                if (tNeueMinute < 0)
                {
                    tNeueMinute= -1 * tNeueMinute;
                }
                
                //Berechnen wieviele Stunden abgezogen werden müssen
                int tStunden = minuten/60;
                //Es kommt vor das z.b. von 00 - 1 = 59 entsteht, hier wird durch mod aber keine Stunde abgezogen,
                //Daher muss "künstlich" eine Stunde generiert werden die abgezogen werden soll..
                if (tStunden == 0)
                {
                    tStunden = 1;
                }
                //Die Berechneten Stunden abziehen.
                SubStunden(tStunden);

                int tNorm = tNeueMinute%60;
                Minute = 60 - tNorm;
            }
            else
            {
                Minute = tNeueMinute;
            }

            return Minute;
        }
       #endregion

       #region Events
       /// <summary>
       /// Bevor der Text eingefügt wird, wird geprüft ob dieser der Maske entspricht. In unserem speziellem Falle, der Zeitmaske dir wir hinterlegt haben für "hh:mm"
       /// </summary>
       protected override void OnPreviewTextInput(TextCompositionEventArgs e)
       {
           System.ComponentModel.MaskedTextResultHint tHint;
           NewTextIsOk = false;

           if (e.Text.Length == 1)
           {
               //Prüfen ob der Wert der eingegeben wurde der Maske entspricht die angegeben wurde, dann handelt es sich schon einmal um eine Zahl.
               if (_mask.VerifyChar(e.Text[0], this.CaretIndex, out tHint))
               {
                   int tCurrentValue = int.Parse(e.Text[0].ToString());
                   //Prüfen an welcher Stelle die Zahl int tCurrentValue eingefügt werden soll.
                   switch (this.CaretIndex)
                   {
                       //Der Erste Wert der Stunde
                       case 0:
                           //an erster Stelle ist nur eine 0, 1 oder 2 zulässig für z.B. 08 Uhr, 12 Uhr oder 22 Uhr
                           if (tCurrentValue == 2 || tCurrentValue == 1 || tCurrentValue == 0)
                           {
                               this.NewTextIsOk = true;

                               int tStunde = 0;
                               //Heraussuchen, wie groß der "zweite" Stundenanteil ist.
                               if (Stunde < 10 && Stunde >= 0)
                               {
                                   tStunde = Stunde;
                               }
                               else if (Stunde > 9 && Stunde < 25)
                               {
                                   tStunde = int.Parse(string.Format("{0}", Stunde.ToString()[1]));
                               }

                               //Wenn es sich bei der aktuellen Stunde um 01 - 09 handelt muss nur aufgepasst werden wenn der CurrentValue = 2 ist.
                               if (tCurrentValue == 2 && tStunde > 3)
                               {
                                   Stunde = 20;
                                   SetText();
                               }
                               else
                               {
                                   //Prüfen ob aktuell ein "einstelliger" stundenbereich in Stunden hinterlegt wurde z.B. "9" -> "09" oder "2" -> "02", ...
                                   if (Stunde < 10 && Stunde >= 0)
                                   {
                                       //Die neue Stunde setzt sich aus dem aktuellen Wert zusammen + den Wert der bereits in Stunden enthalten war,
                                       //d.h. es wird aus "09" -> tCurrentValue=1 -> "19"
                                       Stunde = int.Parse(string.Format("{0}{1}",tCurrentValue.ToString() ,Stunde.ToString()));
                                   }
                                   else if (Stunde > 9 && Stunde < 25)
                                   {
                                       //Es handelt sich um den Zweistelligen Stundenbereich, dann muss beim zusammenstellen des Wertes nur der Zweite Teil genommen werden,
                                       //z.B. bei "19" wird von der Stunde nur die "9" genommen und mit dem tCurrentValue zusammengesetzt.
                                       Stunde = int.Parse(string.Format("{0}{1}", tCurrentValue.ToString(), Stunde.ToString()[1]));
                                   }
                               }
                           }
                           break;

                       //Der Zweite Wert der Stunde
                       case 1:
                           //Es wird ebenfalls der Wert der ersten Stelle benötigt, denn davon hängt ab welche Werte an zweiter Stelle zulässig sind. bei 0 und 1 bis 9 alles zulässig und bei 2 nur bis 3
                           if (Stunde > 19)
                           {
                               //Wenn es sich um die 20++ handelt dann ist als Wert nur noch 0, 1, 2 und 3 zulässig
                               if (tCurrentValue == 0 || tCurrentValue == 1 || tCurrentValue == 2 || tCurrentValue == 3)
                               {
                                   this.NewTextIsOk = true;
                               }
                           }
                           else
                           {
                               this.NewTextIsOk = true;
                           }

                           //Wenn die Eingabe OK war, dann wird der aktuelle Wert der Stunde gutgeschrieben
                           if (NewTextIsOk)
                           {
                               //Prüfen ob aktuell ein "einstelliger" stundenbereich in Stunden hinterlegt wurde z.B. "9" -> "09" oder "2" -> "02", ...
                               if (Stunde < 10 && Stunde >= 0)
                               {
                                   //Wenn es sich um eine einstellige Stunde handelt die vorher angeben wurde und jetzt auch wieder nur der Stundenwert angepasst wurde,
                                   //dann kann der Wert direkt der Stunde zugeordnet werden.
                                   Stunde = tCurrentValue;
                               }
                               else if (Stunde > 9 && Stunde < 25)
                               {
                                   //Beim Zweiten Teil der Stunden wird der erste beibehalten und der zweite "addiert"
                                   Stunde = int.Parse(string.Format("{0}{1}", Stunde.ToString()[0], tCurrentValue.ToString()));
                               } 
                           }
                           break;
                       
                       //Der Erste Wert Der Minute
                       case 3:
                           if (tCurrentValue <= 5)
                           {
                               this.NewTextIsOk = true;
                               //Prüfen ob aktuell ein "einstelliger" minutenbereich in Minuten hinterlegt wurde z.B. "9" -> "09" oder "5" -> "05", ...
                               if (Minute < 10 && Minute >= 0)
                               {
                                   //Die neue Minute setzt sich aus dem aktuellen Wert zusammen + den Wert der bereits in Minuten enthalten war,
                                   //d.h. es wird aus "09" -> tCurrentValue=4 -> "49"
                                   Minute = int.Parse(string.Format("{0}{1}", tCurrentValue.ToString(), Minute.ToString()));
                               }
                               else if (Minute > 9 && Minute < 59)
                               {
                                   //Es handelt sich um den Zweistelligen Minutenbereich, dann muss beim zusammenstellen des Wertes nur der Zweite Teil genommen werden,
                                   //z.B. bei "29" wird von der Stunde nur die "9" genommen und mit dem tCurrentValue zusammengesetzt.
                                   Minute = int.Parse(string.Format("{0}{1}", tCurrentValue.ToString(), Minute.ToString()[1]));
                               } 
                           }
                           break;

                       //ZweiterWertMinute
                       case 4:
                           this.NewTextIsOk = true;
                           //Prüfen ob aktuell ein "einstelliger" minutenbereich in Minuten hinterlegt wurde z.B. "9" -> "09" oder "5" -> "05", ...
                           if (Minute < 10 && Minute >= 0)
                           {
                               //Wenn es sich um eine einstellige Minute handelt die vorher angeben wurde und jetzt auch wieder nur der Minutenwert angepasst wurde,
                               //dann kann der Wert direkt der Minute zugeordnet werden.
                               Minute = tCurrentValue;
                           }
                           else if (Minute > 9 && Minute < 59)
                           {
                               //Beim Zweiten Teil der Minuten wird der erste beibehalten und der zweite "addiert"
                               Minute = int.Parse(string.Format("{0}{1}", Minute.ToString()[0], tCurrentValue.ToString()));
                           } 
                           break;
                   }
               }
           }
           else
           {int tTestPosition;
               this.NewTextIsOk = _mask.VerifyString(e.Text, out tTestPosition, out tHint);
           }
       }

       /// <summary>
       /// Prüfen was für eine Taste geklickt wurde und hier einige Tasten direkt "deaktivieren"
       /// </summary>
       protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
       {
           //Austellen, das Text makiert werden kann
           if (this.SelectionLength > 1)
           {
               this.SelectionLength = 0;
               e.Handled = true;
           }

           //Überrprüfen welche Tasten noch deaktivert werden sollen und somit keine Aktion in der Textbox ausführen sollen
           if (e.Key == Key.Insert || e.Key == Key.Delete || e.Key == Key.Back || (e.Key == Key.Space && IgnoreSpaceKey))
           {
               e.Handled = true;
           }

           //Zusätzlich die KEY->UP und KEY-DOWN Events abfangen, damit man auch mit den Pfeiltasten die Werte ändern kann.
           //Wir befinden uns mit der Anzeige im Textfeld in den Stunden
           if (this.CaretIndex == 0 || this.CaretIndex == 1 || this.CaretIndex == 2)
           {
               //Delta gibt an in welche Richtung das Mausrad gedreht wurde mit + und - vor der Zahl in Delta
               if (e.Key == Key.Up)
               {
                   AddStunden(1);
                   SetText();
               }

               if (e.Key == Key.Down)
               {
                   SubStunden(1);
                   SetText();
               }

               //Wenn im Ersten Element (Im Stundenbereich) ein Tab geklickt wird, wird automatisch in den Minutenbereich gesprungen
               if (e.Key == Key.Tab)
               {
                   this.CaretIndex = 3;
                   e.Handled = true;
               }
           }

           //Wir befinden uns mit der Anzeige im Textfeld in den Minuten
           if (this.CaretIndex == 3 || this.CaretIndex == 4 || this.CaretIndex == 5)
           {
               if (e.Key == Key.Up)
               {
                   AddMinuten(1);
                   SetText();
               }

               if (e.Key == Key.Down)
               {
                   SubMinuten(1);
                   SetText();
               }
           }

           base.OnPreviewKeyDown(e);
       }

        /// <summary>
        /// Wenn das Mausrad gedreht wird, dann wird überprüft ob man sich im Minuten oder Stundenbereich befindet und dann der jeweilige Wert erhöht oder erniedrigt.
        /// </summary>
       protected override void OnMouseWheel(MouseWheelEventArgs e)
       {
           //Wir befinden uns mit der Anzeige im Textfeld in den Stunden
          if (this.CaretIndex == 0 || this.CaretIndex == 1 || this.CaretIndex == 2)
          {
              //Delta gibt an in welche Richtung das Mausrad gedreht wurde mit + und - vor der Zahl in Delta
              if (e.Delta > 0)
              {
                  AddStunden(1);
              }
              else
              {
                  SubStunden(1);
              }
          }

           //Wir befinden uns mit der Anzeige im Textfeld in den Minuten
          if (this.CaretIndex == 3 || this.CaretIndex == 4 || this.CaretIndex == 5)
          {
              if (e.Delta > 0)
              {
                  AddMinuten(1);
              }
              else
              {
                  SubMinuten(1);
              }
          }

           e.Handled = true;
           //Setzten des aktuellen Textes
           SetText();
           base.OnMouseWheel(e);
       }

        /// <summary>
        /// Sobald sich der Text ändert, wird geprüft ob die Zeit auch "richtig" ist. Vor allem wichtig wenn die MaskedTimeTextbox initialisiert wird und hier ungültige Stunden und Minutenwerte übergeben werden.
        /// </summary>
        /// <param name="e"></param>
       protected override void OnTextChanged(TextChangedEventArgs e)
       {
          CheckTime(this.Text);
          base.OnTextChanged(e);
       }
        #endregion
    }

}
